חקור תכונות TypeScript מתקדמות כמו סוגי ליטרל תבנית וסוגים מותנים כדי לכתוב קוד מפורש יותר וניתן לתחזוקה. שלוט במניפולציית סוגים עבור תרחישים מורכבים.
סוגי TypeScript מתקדמים: שליטה בסוגי ליטרל תבנית וסוגים מותנים
הכוח של TypeScript טמון במערכת הסוגים החזקה שלו. בעוד שסוגים בסיסיים כמו string, number ו-boolean מספיקים עבור תרחישים רבים, תכונות מתקדמות כמו סוגי ליטרל תבנית וסוגים מותנים פותחות רמה חדשה של ביטוי ובטיחות סוגים. מדריך זה מספק סקירה מקיפה של סוגים מתקדמים אלה, בוחן את יכולותיהם ומדגים יישומים מעשיים.
הבנת סוגי ליטרל תבנית
סוגי ליטרל תבנית בנויים על ליטרלים תבנית של JavaScript, ומאפשרים לך להגדיר סוגים המבוססים על אינטרפולציה של מחרוזות. זה מאפשר יצירת סוגים המייצגים דפוסי מחרוזת ספציפיים, מה שהופך את הקוד שלך לאמין וצפוי יותר.
תחביר ושימוש בסיסיים
סוגי ליטרל תבנית משתמשים בגרשיים הפוכות (`) כדי לתחום את הגדרת הסוג, בדומה לליטרלים תבנית של JavaScript. בתוך הגרשיים ההפוכות, אתה יכול לאינטרפל סוגים אחרים באמצעות התחביר ${}. כאן מתרחש הקסם – אתה בעצם יוצר סוג שהוא מחרוזת, שנבנית בזמן הידור על סמך הסוגים בתוך האינטרפולציה.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/${string}`;
// Example Usage
const getEndpoint: APIEndpoint = "/api/users"; // Valid
const postEndpoint: APIEndpoint = "/api/products/123"; // Valid
const invalidEndpoint: APIEndpoint = "/admin/settings"; // TypeScript will not show an error here as `string` can be anything
בדוגמה זו, APIEndpoint הוא סוג שמייצג כל מחרוזת המתחילה ב-/api/. בעוד שדוגמה בסיסית זו שימושית, הכוח האמיתי של סוגי ליטרל תבנית צץ כאשר משלבים אותה עם אילוצי סוג ספציפיים יותר.
שילוב עם סוגי איחוד
סוגי ליטרל תבנית באמת זורחים כאשר משתמשים בהם עם סוגי איחוד. זה מאפשר לך ליצור סוגים המייצגים קבוצה ספציפית של שילובי מחרוזות.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIPath = "users" | "products" | "orders";
type APIEndpoint = `/${APIPath}/${HTTPMethod}`;
// Valid API Endpoints
const getUsers: APIEndpoint = "/users/GET";
const postProducts: APIEndpoint = "/products/POST";
// Invalid API Endpoints (will result in TypeScript errors)
// const invalidEndpoint: APIEndpoint = "/users/PATCH"; // Error: "/users/PATCH" is not assignable to type "/users/GET" | "/users/POST" | "/users/PUT" | "/users/DELETE" | "/products/GET" | "/products/POST" | ... 3 more ... | "/orders/DELETE".
כעת, APIEndpoint הוא סוג מגביל יותר המאפשר רק שילובים ספציפיים של נתיבי API ושיטות HTTP. TypeScript יסמן כל ניסיון להשתמש בשילובים לא חוקיים, מה שמשפר את בטיחות הסוגים.
מניפולציית מחרוזות עם סוגי ליטרל תבנית
TypeScript מספקת סוגי מניפולציית מחרוזות פנימיים שעובדים בצורה חלקה עם סוגי ליטרל תבנית. סוגים אלה מאפשרים לך לשנות מחרוזות בזמן הידור.
- Uppercase: הופך מחרוזת לאותיות רישיות.
- Lowercase: הופך מחרוזת לאותיות קטנות.
- Capitalize: הופך את האות הראשונה של מחרוזת לאות גדולה.
- Uncapitalize: הופך את האות הראשונה של מחרוזת לאות קטנה.
type Greeting = "hello world";
type UppercaseGreeting = Uppercase; // "HELLO WORLD"
type LowercaseGreeting = Lowercase; // "hello world"
type CapitalizedGreeting = Capitalize; // "Hello world"
type UncapitalizedGreeting = Uncapitalize; // "hello world"
סוגי מניפולציית מחרוזות אלה שימושיים במיוחד ליצירת סוגים באופן אוטומטי בהתבסס על מוסכמות שמות. לדוגמה, אתה יכול לגזור סוגי פעולות משמות אירועים או להפך.
יישומים מעשיים של סוגי ליטרל תבנית
- הגדרת נקודת קצה של API: כפי שהודגם לעיל, הגדרת נקודות קצה של API עם אילוצי סוג מדויקים.
- טיפול באירועים: יצירת סוגים עבור שמות אירועים עם קידומות וסיומות ספציפיות.
- יצירת מחלקת CSS: יצירת שמות מחלקות CSS המבוססים על שמות וסטטוסים של רכיבים.
- בניית שאילתת מסד נתונים: הבטחת בטיחות סוגים בעת בניית שאילתות מסד נתונים.
דוגמה בינלאומית: עיצוב מטבע
דמיין בניית אפליקציה פיננסית התומכת במספר מטבעות. אתה יכול להשתמש בסוגי ליטרל תבנית כדי לאכוף עיצוב מטבעות נכון.
type CurrencyCode = "USD" | "EUR" | "GBP" | "JPY";
type CurrencyFormat = `${number} ${T}`;
const priceUSD: CurrencyFormat<"USD"> = "100 USD"; // Valid
const priceEUR: CurrencyFormat<"EUR"> = "50 EUR"; // Valid
// const priceInvalid: CurrencyFormat<"USD"> = "100 EUR"; // Error: Type 'string' is not assignable to type '`${number} USD`'.
function formatCurrency(amount: number, currency: T): CurrencyFormat {
return `${amount} ${currency}`;
}
const formattedUSD = formatCurrency(250, "USD"); // Type: "250 USD"
const formattedEUR = formatCurrency(100, "EUR"); // Type: "100 EUR"
דוגמה זו מבטיחה שערכי המטבע תמיד מעוצבים עם קוד המטבע הנכון, ומונעת שגיאות אפשריות.
עיון בסוגים מותנים
סוגים מותנים מציגים לוגיקת הסתעפות למערכת הסוגים של TypeScript, ומאפשרים לך להגדיר סוגים התלויים בסוגים אחרים. תכונה זו חזקה להפליא ליצירת הגדרות סוג גמישות וניתנות לשימוש חוזר.
תחביר ושימוש בסיסיים
סוגים מותנים משתמשים במילת המפתח infer ובאופרטור הטרינרי (condition ? trueType : falseType) כדי להגדיר תנאי סוג.
type IsString = T extends string ? true : false;
type StringCheck = IsString; // type StringCheck = true
type NumberCheck = IsString; // type NumberCheck = false
בדוגמה זו, IsString הוא סוג מותנה שבודק אם T ניתן להקצאה ל-string. אם כן, הסוג נפתר ל-true; אחרת, הוא נפתר ל-false.
מילת המפתח infer
מילת המפתח infer מאפשרת לך לחלץ סוג מסוג. זה שימושי במיוחד בעת עבודה עם סוגים מורכבים כמו סוגי פונקציות או סוגי מערכים.
type ReturnType any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType; // type AddReturnType = number
בדוגמה זו, ReturnType מחלץ את סוג ההחזרה של סוג פונקציה T. החלק infer R של הסוג המותנה מסיק את סוג ההחזרה ומקצה אותו למשתנה הסוג R. אם T אינו סוג פונקציה, הסוג נפתר ל-any.
סוגים מותנים מפיצים
סוגים מותנים הופכים מפיצים כאשר פרמטר הסוג הנבדק הוא פרמטר סוג עירום. משמעות הדבר היא שהסוג המותנה מוחל על כל חבר בסוג האיחוד בנפרד.
type ToArray = T extends any ? T[] : never;
type NumberOrStringArray = ToArray; // type NumberOrStringArray = string[] | number[]
בדוגמה זו, ToArray ממירה סוג T לסוג מערך. מכיוון ש-T הוא פרמטר סוג עירום (לא עטוף בסוג אחר), הסוג המותנה מוחל על number ו-string בנפרד, וכתוצאה מכך איחוד של number[] ו-string[].
יישומים מעשיים של סוגים מותנים
- חילוץ סוגי החזרה: כפי שהודגם לעיל, חילוץ סוג ההחזרה של פונקציה.
- סינון סוגים מאיחוד: יצירת סוג המכיל רק סוגים ספציפיים מאיחוד.
- הגדרת סוגי פונקציות עמוסות יתר על המידה: יצירת סוגי פונקציות שונים על סמך סוגי קלט.
- יצירת שמרי סוג: הגדרת פונקציות המצמצמות את סוג המשתנה.
דוגמה בינלאומית: טיפול בפורמטי תאריך שונים
אזורים שונים בעולם משתמשים בפורמטי תאריכים שונים. אתה יכול להשתמש בסוגים מותנים כדי לטפל בווריאציות אלה.
type DateFormat = "YYYY-MM-DD" | "MM/DD/YYYY" | "DD.MM.YYYY";
type ParseDate = T extends "YYYY-MM-DD"
? { year: number; month: number; day: number; format: "YYYY-MM-DD" }
: T extends "MM/DD/YYYY"
? { month: number; day: number; year: number; format: "MM/DD/YYYY" }
: T extends "DD.MM.YYYY"
? { day: number; month: number; year: number; format: "DD.MM.YYYY" }
: never;
function parseDate(dateString: string, format: T): ParseDate {
// (Implementation would handle different date formats)
if (format === "YYYY-MM-DD") {
const [year, month, day] = dateString.split("-").map(Number);
return { year, month, day, format } as ParseDate;
} else if (format === "MM/DD/YYYY") {
const [month, day, year] = dateString.split("/").map(Number);
return { month, day, year, format } as ParseDate;
} else if (format === "DD.MM.YYYY") {
const [day, month, year] = dateString.split(".").map(Number);
return { day, month, year, format } as ParseDate;
} else {
throw new Error("Invalid date format");
}
}
const parsedDateISO = parseDate("2023-10-27", "YYYY-MM-DD"); // Type: { year: number; month: number; day: number; format: "YYYY-MM-DD"; }
const parsedDateUS = parseDate("10/27/2023", "MM/DD/YYYY"); // Type: { month: number; day: number; year: number; format: "MM/DD/YYYY"; }
const parsedDateEU = parseDate("27.10.2023", "DD.MM.YYYY"); // Type: { day: number; month: number; year: number; format: "DD.MM.YYYY"; }
console.log(parsedDateISO.year); // Access the year knowing it will be there
דוגמה זו משתמשת בסוגים מותנים כדי להגדיר פונקציות ניתוח תאריכים שונות על סמך פורמט התאריך שצוין. הסוג ParseDate מבטיח שלאובייקט המוחזר יש את המאפיינים הנכונים על סמך הפורמט.
שילוב של סוגי ליטרל תבנית וסוגים מותנים
הכוח האמיתי מגיע כאשר אתה משלב סוגי ליטרל תבנית וסוגים מותנים. זה מאפשר מניפולציות סוג חזקות להפליא.
type EventName = `on${Capitalize}`;
type ExtractEventPayload = T extends EventName
? { type: T; payload: any } // Simplified for demonstration
: never;
type ClickEvent = EventName<"click">; // "onClick"
type MouseOverEvent = EventName<"mouseOver">; // "onMouseOver"
//Example function that takes a type
function processEvent(event: T): ExtractEventPayload {
//In a real implementation, we would actually dispatch the event.
console.log(`Processing event ${event}`);
//In a real implementation, the payload would be based on event type.
return { type: event, payload: {} } as ExtractEventPayload;
}
//Note that the return types are very specific:
const clickEvent = processEvent("onClick"); // { type: "onClick"; payload: any; }
const mouseOverEvent = processEvent("onMouseOver"); // { type: "onMouseOver"; payload: any; }
//If you use other strings, you get never:
// const someOtherEvent = processEvent("someOtherEvent"); // Type is `never`
שיטות עבודה מומלצות ושיקולים
- שמור על זה פשוט: בעודם חזקים, סוגים מתקדמים אלה יכולים להפוך למורכבים במהירות. חתור לשקיפות ויכולת תחזוקה.
- בדוק ביסודיות: ודא שהגדרות הסוג שלך מתנהגות כמצופה על ידי כתיבת בדיקות יחידות מקיפות.
- תעד את הקוד שלך: תיעד בבירור את המטרה וההתנהגות של הסוגים המתקדמים שלך כדי לשפר את קריאות הקוד.
- שקול ביצועים: שימוש מופרז בסוגים מתקדמים יכול להשפיע על זמן ההידור. בצע פרופיל לקוד שלך ואופטימיזציה במידת הצורך.
סיכום
סוגי ליטרל תבנית וסוגים מותנים הם כלים רבי עוצמה בארסנל של TypeScript. על ידי שליטה בסוגים מתקדמים אלה, אתה יכול לכתוב קוד מפורש, ניתן לתחזוקה ובטוח יותר מסוג. תכונות אלה מאפשרות לך ללכוד קשרים מורכבים בין סוגים, לאכוף אילוצים מחמירים יותר וליצור הגדרות סוג הניתנות לשימוש חוזר. אמץ טכניקות אלה כדי להעלות את כישורי ה-TypeScript שלך ולבנות אפליקציות חזקות וניתנות להרחבה עבור קהל גלובלי.